package com.stars.easyms.base.listener.spring;

import com.stars.easyms.base.util.AnnotationUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * <p>className: EasyMsSpringValueRegister</p>
 * <p>description: EasyMs实现@Value注解注册器</p>
 *
 * @author guoguifang
 * @version 1.7.3
 * @date 2021/3/22 12:22 下午
 */
public class EasyMsSpringValueRegister implements BeanPostProcessor, PriorityOrdered {

    static final Map<String, List<EasyMsSpringValueContext>> REGISTRY = new ConcurrentHashMap<>(32);

    private static final String PLACEHOLDER_PREFIX = "${";
    private static final String PLACEHOLDER_SUFFIX = "}";
    private static final String VALUE_SEPARATOR = ":";
    private static final String SIMPLE_PLACEHOLDER_PREFIX = "{";
    private static final String EXPRESSION_PREFIX = "#{";
    private static final String EXPRESSION_SUFFIX = "}";

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Class clazz = bean.getClass();
        ReflectionUtils.doWithFields(clazz,
                field -> processField(bean, beanName, field),
                field -> AnnotationUtil.hasAnnotation(field, Value.class));
        ReflectionUtils.doWithMethods(clazz,
                method -> processMethod(bean, beanName, method),
                method -> {
                    // 方法必须是set开头并且参数个数为1
                    if (!method.getName().startsWith("set") || method.getParameterTypes().length != 1) {
                        return false;
                    }
                    // 如果方法或者参数有@Value注解则通过
                    return AnnotationUtil.hasAnnotation(method, Value.class)
                            || AnnotationUtil.hasAnnotation(method.getParameters()[0], Value.class);
                });
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    private void processField(Object bean, String beanName, Field field) {
        Value valueAnnotation = field.getDeclaredAnnotation(Value.class);
        if (valueAnnotation == null) {
            return;
        }
        String value = valueAnnotation.value();
        Set<String> placeHolderKeys = extractPlaceholderKeys(value);
        if (!placeHolderKeys.isEmpty()) {
            placeHolderKeys.forEach(
                    placeHolderKey -> REGISTRY.computeIfAbsent(placeHolderKey, v -> new CopyOnWriteArrayList<>())
                            .add(new EasyMsSpringValueContext(field, value, bean, beanName)));
        }
    }

    private void processMethod(Object bean, String beanName, Method method) {
        Value valueAnnotation = method.getDeclaredAnnotation(Value.class);
        if (valueAnnotation == null) {
            valueAnnotation = method.getParameters()[0].getDeclaredAnnotation(Value.class);
            if (valueAnnotation == null) {
                return;
            }
        }
        String value = valueAnnotation.value();
        Set<String> placeHolderKeys = extractPlaceholderKeys(value);
        if (!placeHolderKeys.isEmpty()) {
            placeHolderKeys.forEach(
                    placeHolderKey -> REGISTRY.computeIfAbsent(placeHolderKey, v -> new CopyOnWriteArrayList<>())
                            .add(new EasyMsSpringValueContext(method, value, bean, beanName)));
        }
    }

    private Set<String> extractPlaceholderKeys(String propertyString) {
        Set<String> placeholderKeys = new HashSet<>();

        if (StringUtils.isEmpty(propertyString)
                || (!isNormalizedPlaceholder(propertyString) && !isExpressionWithPlaceholder(propertyString))) {
            return placeholderKeys;
        }

        Stack<String> stack = new Stack<>();
        stack.push(propertyString);

        while (!stack.isEmpty()) {
            String strVal = stack.pop();
            int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
            if (startIndex == -1) {
                placeholderKeys.add(strVal);
                continue;
            }
            int endIndex = findPlaceholderEndIndex(strVal, startIndex);
            if (endIndex == -1) {
                continue;
            }

            String placeholderCandidate = strVal.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);

            if (placeholderCandidate.startsWith(PLACEHOLDER_PREFIX)) {
                stack.push(placeholderCandidate);
            } else {
                int separatorIndex = placeholderCandidate.indexOf(VALUE_SEPARATOR);

                if (separatorIndex == -1) {
                    stack.push(placeholderCandidate);
                } else {
                    stack.push(placeholderCandidate.substring(0, separatorIndex));
                    String defaultValuePart =
                            normalizeToPlaceholder(placeholderCandidate.substring(separatorIndex + VALUE_SEPARATOR.length()));
                    if (!StringUtils.isEmpty(defaultValuePart)) {
                        stack.push(defaultValuePart);
                    }
                }
            }

            if (endIndex + PLACEHOLDER_SUFFIX.length() < strVal.length() - 1) {
                String remainingPart = normalizeToPlaceholder(strVal.substring(endIndex + PLACEHOLDER_SUFFIX.length()));
                if (!StringUtils.isEmpty(remainingPart)) {
                    stack.push(remainingPart);
                }
            }
        }

        return placeholderKeys;
    }

    private boolean isNormalizedPlaceholder(String propertyString) {
        return propertyString.startsWith(PLACEHOLDER_PREFIX) && propertyString.contains(PLACEHOLDER_SUFFIX);
    }

    private boolean isExpressionWithPlaceholder(String propertyString) {
        return propertyString.startsWith(EXPRESSION_PREFIX) && propertyString.contains(EXPRESSION_SUFFIX)
                && propertyString.contains(PLACEHOLDER_PREFIX) && propertyString.contains(PLACEHOLDER_SUFFIX);
    }

    private String normalizeToPlaceholder(String strVal) {
        int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
        if (startIndex == -1) {
            return null;
        }
        int endIndex = strVal.lastIndexOf(PLACEHOLDER_SUFFIX);
        if (endIndex == -1) {
            return null;
        }
        return strVal.substring(startIndex, endIndex + PLACEHOLDER_SUFFIX.length());
    }

    private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
        int index = startIndex + PLACEHOLDER_PREFIX.length();
        int withinNestedPlaceholder = 0;
        while (index < buf.length()) {
            if (StringUtils.substringMatch(buf, index, PLACEHOLDER_SUFFIX)) {
                if (withinNestedPlaceholder > 0) {
                    withinNestedPlaceholder--;
                    index = index + PLACEHOLDER_SUFFIX.length();
                } else {
                    return index;
                }
            } else if (StringUtils.substringMatch(buf, index, SIMPLE_PLACEHOLDER_PREFIX)) {
                withinNestedPlaceholder++;
                index = index + SIMPLE_PLACEHOLDER_PREFIX.length();
            } else {
                index++;
            }
        }
        return -1;
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }

}